// John Lake
// CSCE 441

#ifndef RAY_TRACER_H
#define RAY_TRACER_H


#include "./constants.h"
#include <stdlib.h>
#include <math.h>
#include <vector>
#include <list>
#include <algorithm>



class Object;

class Point {
public:
	float x, y, z;
	
	Point() :
	   x(0), y(0), z(0)  { }

	Point(float _x, float _y, float _z) :
	   x(_x), y(_y), z(_z)  { }

	~Point()  { }
	
	float magnitude();
	void normalize();
	Point operator-(Point& rhs);
	Point operator+(Point& rhs);
	float operator*(Point& rhs);	// dot product
	Point operator*(float& rhs);	// scalar product (scalar on the RHS)
	Point operator/(float& rhs);
	
};

typedef Point Normal;	// really a vector, but having std::vector and Vector could get confusing


/**********************************************************************************/

class SpherePoint {
public:
	float radius, theta, phi;	// theta is azimuth, phi is elevation from north pole
	Point center;				// will do everything with respect to a center point

	SpherePoint() :
		radius(0), theta(0), phi(0), center(Point())  { }

	SpherePoint(float _radius, float _theta, float _phi, Point _center) :
		radius(_radius), theta(_theta), phi(_phi), center(_center)  { }

	~SpherePoint()  { }

};
/**********************************************************************************/

class Ray {
public:
	Ray() :
	  point(Point()), vector(Normal())  { vector.normalize(); }	

	Ray(Point _p, Normal _v) :
		point(_p), vector(_v)  { vector.normalize(); }
	
	~Ray()  { }
	
	Point p();
	Normal v();
	
	Point at(float parameter);
	float magnitude();
	
	Point point;
	Normal vector;	
};


/**********************************************************************************/

class Color {
public:
	Color() :
	   r(0), g(0), b(0)  { }

	Color(float _r, float _g, float _b) :
	   r(_r), g(_g), b(_b)  { }

	~Color()  { }
	
	void clamp();
	float r, g, b;		// Color (R,G,B values)

	Color operator+(Color& rhs);
	Color operator*(float& rhs);
};


/**********************************************************************************/

// assuming isotropic lights - i.e. not a flashlight
class Light {
public:	// would be better if it was protected
	Light() :
	  c(Color()), l(Point())  { }

	Light(Color _color, Point _point) :
	  c(_color), l(_point) { }

	~Light()  { }
	
	Point location();
	Color color();
	float intersection(Ray _ray);

	Color c;
	Point l;
};


/**********************************************************************************/

class LightingPackage {
public:
	std::vector<Light> lights;
	std::vector<Object*> objects;

	LightingPackage()  { }

	LightingPackage(std::vector<Light> _lights, std::vector<Object*> _objects) :
		lights(_lights), objects(_objects)  { }

	~LightingPackage()  { }

};


/**********************************************************************************/

class Matrix
{
public:
	float data [ 3 ] [ 3 ];	// = data [row][column]

	Matrix ( void )
	{
		int i, j;

		for ( i = 0; i < 3; i++ )
		{
			for ( j = 0; j < 3; j++ )
			{
				data [ i ] [ j ] = 0;
			}
		}
	}

	Matrix transpose();
	Point operator*(Point& rhs);	// Matrix * Point (or Normal; isPoint tells which)

};


/**********************************************************************************/

class Object {
public:
	Object();
	~Object()  { }
	
	Color ka, kd, ks;
	Matrix deformationInverse;	// will need to initialize to I ?
	int index;	// used to compare the Objects  (or could do a  if(&Object == this)	// will probably do the latter
	float refl_coeff;	// used to scale down the contribution of reflected rays to a factor

	void setKa(Color _ka);
	void setKd(Color _kd);
	void setKs(Color _ks);
	void setReflCoeff(float _gamma);
	virtual Color getKa(Point p);	// will be redefined for my planets, if I have time
	virtual Color getKd(Point p);
	virtual Color getKs(Point p);

	virtual Normal getNormal(Point p) =0;
	virtual float intersection(Ray ray) =0;	// return negative number if no intersections


	void setDeformationInverse(Matrix _deformationInverse);
	
	Color lightingFunction(Ray ray, float t, float depth, Color ambientLight, LightingPackage lp);	

	
};

/**/


class Plane : public Object {
public:
	Plane()  { }	// hopefully never used
	Plane(Normal _normal, Point _point, int _index) :
		normal(_normal), pointOnPlane(_point)  {  index = _index; normal.normalize(); }
	
	~Plane()  { }
	
	Normal normal;
	Point pointOnPlane;

	float intersection(Ray ray);
	Normal getNormal(Point p);
	
};

/**/


class Cylinder : public Object {
public:
	Cylinder();
	Cylinder(Point _center, Normal _axis, float _radius, int _index):
		center(_center), axis(_axis), radius(_radius)  {  index = _index; axis.normalize(); }
	
	~Cylinder()  { }
	
	Point center;
	Normal axis;
	float radius;	// magnitude

	float intersection(Ray ray);
	Normal getNormal(Point p);

};

/**/


class Sphere : public Object {
public:
	Sphere() { }	// hopefully never used
	Sphere(Point _center, float _radius, int _index) :
		center(_center), radius(_radius)  {  index = _index; }
	
	~Sphere()  { }
	
	Point center;
	float radius;	// magnitude

	float intersection(Ray ray);
	Normal getNormal(Point p);

};


/**/

class Earth : public Sphere {
public:
	Earth();
	Earth(Point _center, float _radius, int _index) {  center = _center; radius = _radius; index = _index;  }
	
	~Earth() { }


	bool isOnContinent(SpherePoint sp);
	bool isOnIceCap(SpherePoint sp);
	Color getKa(Point p);	
	Color getKd(Point p);
	Color getKs(Point p);

};


/**/

class Jupiter : public Sphere {
public:
	Jupiter();
	Jupiter(Point _center, float _radius, int _index) {  center = _center; radius = _radius; index = _index;  }
	
	~Jupiter() { }


	bool isRedSpot(Point p);
	bool isYellow(SpherePoint sp);
	bool isOrange(SpherePoint sp);
	bool isWhite(SpherePoint sp);
	bool isBrown(SpherePoint sp);
	Color getKa(Point p);	
	Color getKd(Point p);
	Color getKs(Point p);

};


/**/

class Neptune : public Sphere {
public:
	Neptune();
	Neptune(Point _center, float _radius, int _index) {  center = _center; radius = _radius; index = _index;  }
	
	~Neptune() { }


	bool isDarkSpot(Point p);
	Color getKa(Point p);
	Color getKd(Point p);
	Color getKs(Point p);

};


/**/

// Kind of a fake CSG tree I wrote for one purpose - planetary rings
class Ring : public Object {
public:
	Ring();	// empty until sublcasses defined
	Ring(Point _center, float _radius1, float _radius2, Normal normal, int index);

	~Ring()  { }
	
	Sphere inner, outer;
	Plane plane;

	Normal getNormal(Point p);
	float intersection(Ray ray);	// return negative number if no intersections


};


#endif